﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.ComponentModel;
using ClassifierLib.Classifier.Filters;
using System.IO;

namespace ClassifierLib.Classifier
{
    public class IterationArgs : System.EventArgs
    {

        private Population _population;

        public IterationArgs(Population Population)
        {
            this._population = Population;
        }

        public Population Population
        {
            get
            {
                return _population;
            }
        }
    }

    public class StepArgs : System.EventArgs
    {

        private Population _population;
        private Population _selected;

        public StepArgs(Population Population, Population Selected)
        {
            this._population = Population;
            this._selected = Selected;
        }

        public Population Population
        {
            get
            {
                return _population;
            }
        }

        public Population Selected
        {
            get
            {
                return _selected;
            }
        }
    }

    public class LCS
    {
        private Population _rules;
        private List<Filters.RuleFilter> _filters;
        private int _iterationCount = 0;

        public int IterationCount
        {
            get { return _iterationCount; }
            set { _iterationCount = value; }
        }

        // delegate declaration 
        public delegate void IterationHandlerEvent(object sender, IterationArgs args);
        public delegate void StepHandlerEvent(object sender, StepArgs args);
        public delegate void TrainingStartedEvent(object sender, EventArgs args);
        public delegate void TrainingEndedEvent(object sender, EventArgs args);

        // event declaration 
        public event IterationHandlerEvent IterationComplete;
        public event StepHandlerEvent RuleComplete;
        public event TrainingStartedEvent TrainingStarted;
        public event TrainingEndedEvent TrainingEnded;

        // global sum of current performance, needs to be reset every 100 iterations
        private int _sumLCSPerformance;

        public int SumLCSPerformance
        {
            get { return _sumLCSPerformance; }
            set { _sumLCSPerformance = value; }
        }

        private double _iterationsToAveragePerformance = 100;
        private Queue<double> _LCSRecentPerformance = new Queue<double>(100);

        /// <summary>
        /// used to add the performance of the most recent iteration to the _LCSRecentPerformance queue
        /// </summary>
        private void addIterationPerformance(double Reward)
        {
            if (_LCSRecentPerformance.Count >= _iterationsToAveragePerformance)
            { 
                _LCSRecentPerformance.Dequeue();
                _LCSRecentPerformance.Enqueue(Reward);
            }
            else
                _LCSRecentPerformance.Enqueue(Reward);
        }

        public double getLCSPerformance
        {
            get { return _LCSRecentPerformance.Average(); }
        }

        private static int _exploitCounter = 0;

        public int ExploitCounter
        {
            get { return _exploitCounter; }
        }

        /// <summary>
        /// Boolean bit to indicate LCS is currently in an exploiting mode
        /// </summary>
        private bool exploiting;

        /// <summary>
        /// Boolean bit to indicate LCS is currently in an exploiting mode
        /// </summary>
        public bool Exploiting
        {
            get { return exploiting; }
        }

        //BPH DEBUG
        private bool _writeLogFile = false;

        private TextWriter tw;// = new StreamWriter(System.IO.Directory.GetCurrentDirectory().Substring(0,System.IO.Directory.GetCurrentDirectory().LastIndexOf("LCS"))+"Debug Output Files\\f_out_" + DateTime.Now.ToString("yyMMdd_HHmmss") + ".csv");
        

        /// <summary>
        /// For each filter call apply function, and pass result to next filter
        /// </summary>
        /// <param name="state"></param>
        /// <param name="action"></param>
        private void ProcessFilters(Environment.State state, Action action)
        {
            lock (Rules)
            {
                if (_writeLogFile)
                {
                    tw.WriteLine("Condition,Action,Numerosity,Prediction,Error,Fitness,Accuracy,Org_Error,Age,NicheSize,Initial Iteration,lastIterationInGA");
                    tw.WriteLine("Iteration Count:," + _iterationCount.ToString());
                }
                Population subset = new Population();

                RuleFilter.IterationCount = _iterationCount;

                foreach (Filters.RuleFilter f in Filters)
                {
                    if (f is ExploreExploitSelect)
                    {
                        ClassifierLib.Classifier.Filters.ExploreExploitSelect ees = (ClassifierLib.Classifier.Filters.ExploreExploitSelect)f;
                        if (ees.ExploreFirst)
                            exploiting = false;
                        else
                        {
                            exploiting = true;
                            _exploitCounter++;
                        }
                    }

                    if (f is Update)
                    {
                        ClassifierLib.Classifier.Filters.Update uf = (ClassifierLib.Classifier.Filters.Update)f;
                        uf.Exploiting = exploiting;
                    }
                    
                    subset = f.Apply(Rules, subset, state, action);
                    //TODO: fire filter processed event

                    if (_writeLogFile)
                        writeToDebugFile(f.Name, Rules, subset, state, action,exploiting);

                    if (f is Update)
                    {
                        ClassifierLib.Classifier.Filters.Update uf = (ClassifierLib.Classifier.Filters.Update)f;
                        //This is depracated by below
                        if (uf.RewardIsGiven && exploiting)
                            SumLCSPerformance++;
                        if (exploiting)
                        {
                            addIterationPerformance(uf.RewardReceived);
                            
                        }
                    }

                    if (RuleComplete != null)
                        RuleComplete(this, new StepArgs(Rules, subset));
                }

                if (_writeLogFile)
                    tw.WriteLine("----------,----------,----------,----------,----------,----------,----------,----------,----------,----------,----------,");
               
            }
        }

        private void writeToDebugFile(String FilterName, Population Rules, Population subset,ClassifierLib.Environment.State state,Action action,bool exploiting)
        {
            tw.WriteLine();
            string filterLine = FilterName + ",";
            if (FilterName == "Explore Exploit Select")
                filterLine += "Exploiting:" + exploiting.ToString();
            tw.WriteLine(filterLine);
            tw.WriteLine("POPULATION,");
            foreach (Rule r in Rules)
            {
                tw.WriteLine(r.Condition.ToString() + "," + r.Action.ToString() + ","
                    + r.Numerosity.ToString() + ","
                    + r.Statistics["prediction"].ToString() + ","
                    + r.Statistics["error"].ToString() + ","
                    + r.Statistics["fitness"].ToString() + ","
                    + r.Statistics["accuracy"].ToString() + ","
                    + r.Statistics["org_error"].ToString() + ","
                    + r.Statistics["age"].ToString() + ","
                    + r.Statistics["nicheSize"].ToString() + ","
                    + r.Statistics["initIter"].ToString() + ","
                    + r.Statistics["lastIterationInGA"].ToString() + ","
                    );
            }
            tw.WriteLine("SUBSET,");
            if (subset != null)
            {
                foreach (Rule r in subset)
                {
                    tw.WriteLine(r.Condition.ToString() + "," + r.Action.ToString() + ","
                        + r.Numerosity.ToString() + ","
                        + r.Statistics["prediction"].ToString() + ","
                        + r.Statistics["error"].ToString() + ","
                        + r.Statistics["fitness"].ToString() + ","
                        + r.Statistics["accuracy"].ToString() + ","
                        + r.Statistics["org_error"].ToString() + ","
                        + r.Statistics["age"].ToString() + ","
                        + r.Statistics["nicheSize"].ToString() + ","
                        + r.Statistics["initIter"].ToString() + ","
                        + r.Statistics["lastIterationInGA"].ToString()+","
                        );
                }
            }
            tw.WriteLine("STATE,");
            string str = "";
            foreach (double d in state.Values)
            {
                str += d.ToString() + ",";
            }
            tw.WriteLine(str);
            tw.WriteLine("ACTION,");
            tw.WriteLine(action.Value.ToString());
        }

        

        /// <summary>
        /// Train a given training set of data
        /// </summary>
        /// <param name="TrainingSet">The training set of training values, consisting of an environment state and desired action</param>
        public virtual void Train(TrainingSet TrainingSet)
        {
            if (TrainingStarted != null)
                TrainingStarted(this, new EventArgs());

            //DEBUG
            if (_writeLogFile)
                tw = new StreamWriter(System.IO.Directory.GetCurrentDirectory().Substring(0, System.IO.Directory.GetCurrentDirectory().LastIndexOf("LCS")) + "Debug Output Files\\f_out_" + DateTime.Now.ToString("yyMMdd_HHmmss") + ".csv");

            AssignActionFactoryToFilters(TrainingSet.ActionFactory);
            
            while (!TrainingSet.IsFinished)
            {
                TrainingValue currentSample = TrainingSet.GetNext();
                ProcessFilters(currentSample.State, currentSample.Action);
                _iterationCount++;
                if (IterationComplete != null)
                    IterationComplete(this, new IterationArgs((Population)Rules));

                //DEBUG - to start debug file after certain iteration count
                //if (_iterationCount == 10000)
                //{
                //    tw = new StreamWriter(System.IO.Directory.GetCurrentDirectory().Substring(0, System.IO.Directory.GetCurrentDirectory().LastIndexOf("LCS")) + "Debug Output Files\\f_out_" + DateTime.Now.ToString("yyMMdd_HHmmss") + ".csv");
                //    _writeLogFile = true;
                //}
            }

            if (TrainingEnded != null)
                TrainingEnded(this, new EventArgs());

            if (_writeLogFile)
                tw.Close();

            //DEBUG
            foreach (Filters.RuleFilter f in Filters)
            {
                if (f is ExploreSelect)
                {
                    ClassifierLib.Classifier.Filters.ExploreSelect es = (ClassifierLib.Classifier.Filters.ExploreSelect)f;
                    es.CloseDebugFile();
                }
            }
        }

        /// <summary>
        /// This function ensures that each filter's actionfactory points to the current training sets action factory
        /// This is needed in cases where the current training set has changed since last training execution
        /// </summary>
        /// <param name="ActionFactory"></param>
        private void AssignActionFactoryToFilters(ActionFactory ActionFactory)
        {
            foreach (RuleFilter filter in Filters)
            {
                filter.ActionFactory = ActionFactory;
            }
        }


        /// <summary>
        /// Current population of the LCS
        /// setter is intended for xml deserialisation of rules, not for modifying population during LCS exectution
        /// </summary>
        [Browsable(false)]
        public Population Rules
        {
            get
            {
                return _rules;
            }
            set
            {
                _rules = value;
            }
        }

        /// <summary>
        /// List of filters used to implement LCS
        /// </summary>
        [Browsable(false)]
        public List<Filters.RuleFilter> Filters
        {
            get
            {
                return _filters;
            }
        }
        /// <summary>
        /// Default constructor, creates standard filters
        /// </summary>
        public LCS()
        {
            _rules = new Population();
            _filters = new List<Filters.RuleFilter>();

        }

        /// <summary>
        /// Creates a default list of filters for implementing an LCS
        /// </summary>
        public void AddDefaultFilters()
        {
            // set default keys on rules
            // Note: these can be moved to filter constructors if they are only needed by 1 filter, ideally should be done in each filter that uses the variable, as should filters be replaced it might break things strangely
            // THESE SHOULD BE MOVED, serialization will be buggy if left here
            Rule.InitialStatistics["prediction"] = 500;
            Rule.InitialStatistics["error"] = 500;
            Rule.InitialStatistics["fitness"] = 0.5;
            Rule.InitialStatistics["accuracy"] = 0.5;
            Rule.InitialStatistics["org_error"] = 1;//10//if r = 1000 not 0.01;
            Rule.InitialStatistics["age"] = 0;
            Rule.InitialStatistics["nicheSize"] = 1;
            Rule.InitialStatistics["initIter"] = 0;

            // explore/exploit behaviour
            _filters.Add(new Filters.Match());
            _filters.Add(new Filters.Cover());
            _filters.Add(new Filters.ExploreExploitSelect());
            _filters.Add(new Filters.Update());
        }

    }
}
